/**
 * Created with IntelliJ IDEA.
 * User: ajhin
 * Date: 9/5/13
 * Time: 10:52 PM
 * To change this template use File | Settings | File Templates.
 */
package com.societyGames.codeAlong.state.codeExercise.model.testCase
{
import com.societyGames.codeAlong.state.codeExercise.model.*;
import com.societyGames.system.EventObject;
import com.societyGames.system.FileCache;
import com.societyGames.utility.asyncStateMachine.StateMachine;

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.TextEvent;
import flash.utils.ByteArray;

public class TestCaseController extends EventDispatcher
{
  internal var _testCases:Vector.<TestCase> = new Vector.<TestCase>();
  private var _selectedTestCase:TestCase; 	//What case are we on?
  internal var _executeByteArray:ByteArray; //The swf to test against. If null, we're not testing against anything, just browsing test cases.
  internal var _successCount:int; 		//How many cases have succeeded?

  private var _selectedCaseChangedEventObject:EventObject; //Event.
  internal var _selectedCaseStatusChangedEventObject:EventObject; //Event. Raised when the current test case status changes.
  internal var _testCasesSucceededEventObject:EventObject; //Event. Raised when all test cases have run and succeeded.
  internal var _testCasesFailedEventObject:EventObject; //Event. Raised when all test cases have run and one failed.
  internal var _exceptionEventObject:EventObject;		//ExceptionEvent: The user code had an exception.
  internal var _unhandledExceptionEventObject:EventObject; //Event: User code exceptioned but nobody caught it.
  private var _outputEventObject:EventObject;			//TextEvent: The text to be outputted
  private var _refreshControlsEventObject:EventObject; //Event: One of the "can_" functions has updated.

  internal var _stateMachine:StateMachine = new StateMachine();

  internal var _fullyResetState:FullyResetState; //Nothing is running, nothing has failed. Everything is reset.
  internal var _waitForCanExecuteState:WaitForCanExecuteState; //Waiting for the executing test case to be ready before executing.
  internal var _executeState:ExecuteState; //Executing the test case.
  internal var _exceptionState:ExceptionState; //Exception occurred
  internal var _failState:FailState; //Test case failed or was stopped or exceptioned
  internal var _delayState:DelayState; //Wait for a period of time before moving onto the next state.
  internal var _successState:SuccessState; //All test cases succeeded

  public function TestCaseController(fileCache:FileCache, caseDatas:Vector.<CaseData>)
  {
    _selectedCaseChangedEventObject = new EventObject(this, "SELECTED_CASE_CHANGED", Event);
    _selectedCaseStatusChangedEventObject = new EventObject(this, "SELECTED_CASE_STATUS_CHANGED", Event);
    _testCasesSucceededEventObject = new EventObject(this, "TEST_CASES_SUCCEEDED", Event);
    _testCasesFailedEventObject = new EventObject(this, "TEST_CASES_FAILED", Event);
    _exceptionEventObject = new EventObject(this, "EXCEPTION", ExceptionEvent);
    _unhandledExceptionEventObject = new EventObject(this, "UNHANDLED_EXCEPTION", Event);
    _outputEventObject = new EventObject(this, "OUTPUT", TextEvent);
    _refreshControlsEventObject = new EventObject(this, "REFRESH_CONTROLS", Event);

    for (var index:int = 0; index < caseDatas.length; index++)
    {
      var caseData:CaseData = caseDatas[index];
      var testCase:TestCase = new TestCase(fileCache, index, caseData);
      _testCases.push(testCase);
    }

    _stateMachine.stateChangedEventObject.addEventListener(handleStateMachineStateChanged);
    _fullyResetState = new FullyResetState(this);
    _waitForCanExecuteState = new WaitForCanExecuteState(this);
    _executeState = new ExecuteState(this);
    _exceptionState = new ExceptionState(this);
    _failState = new FailState(this);
    _delayState = new DelayState(this);
    _successState = new SuccessState(this);

    _stateMachine.gotoState(_fullyResetState);
  }

  private function handleStateMachineStateChanged(event:Event):void
  {
    this._refreshControlsEventObject.dispatchEvent();
  }

  public function get canSelectCase():Boolean
  {
    return this._stateMachine.state is ISelectable;
  }

  public function selectCase(index:int):void
  {
    var selectable:ISelectable = this._stateMachine.state as ISelectable;
    selectable.select(index);
  }

  public function get canExecute():Boolean
  {
    return this._stateMachine.state is IExecutable;
  }

  public function execute(executeByteArray:ByteArray):void
  {
    var executable:IExecutable = this._stateMachine.state as IExecutable;
    executable.execute(executeByteArray);
  }

  public function get canStop():Boolean
  {
    return this._stateMachine.state is IStoppable;
  }

  public function stop():void
  {
    var stoppable:IStoppable = this._stateMachine.state as IStoppable;
    stoppable.stop();
  }

  public function get canReset():Boolean
  {
    return this._stateMachine.state is IResetable;
  }

  public function reset():void
  {
    var resetable:IResetable = this._stateMachine.state as IResetable;
    resetable.reset();
  }

  /**
   * Set the current case to show.
   * @param index The index of the test case to show.
   */
  internal function _selectCase(index:int):void
  {
    _selectedTestCase = _testCases[index];
    this._selectedCaseChangedEventObject.dispatchEvent();
  }

  internal function _resetAllTestCases():void
  {
    for each(var testCase:TestCase in _testCases)
    {
      if (testCase.canReset)
      {
        testCase.reset();
      }
    }
  }

  /**
   * All of the test cases.
   */
  public function get testCases():Vector.<TestCase>
  {
    return _testCases;
  }

  /**
   * The current test case's index. May be -1
   * TODO: Remove this. Only used for display of (1/3) Distracting?
   */
  public function get currentTestCaseIndex():int
  {
    if (_selectedTestCase == null)
    {
      return -1;
    }
    return _selectedTestCase.index;
  }

  public function get selectedTestCase():TestCase
  {
    return _selectedTestCase;
  }

  public function resetCurrentTestCase():void
  {
    if (_executeByteArray != null)
    {
      throw new Error("Cannot resetCurrentTestCase() while execution is running.");
    }
    _selectedTestCase.reset();
  }

  internal function _testCaseOutputHandler(event:TextEvent):void
  {
    this._outputEventObject.dispatchEvent(new TextEvent(this._outputEventObject.type, false, false, event.text));
  }

  //Event
  public function get refreshControlsEventObject():EventObject
  {
    return this._refreshControlsEventObject;
  }

  //Event
  public function get selectedCaseChangedEventObject():EventObject
  {
    return this._selectedCaseChangedEventObject;
  }

  //Event
  public function get selectedCaseStatusChangedEventObject():EventObject
  {
    return this._selectedCaseStatusChangedEventObject;
  }

  //Event
  public function get testCasesSucceededEventObject():EventObject
  {
    return _testCasesSucceededEventObject;
  }

  //Event
  public function get testCasesFailedEventObject():EventObject
  {
    return _testCasesFailedEventObject;
  }

  public function get exceptionEventObject():EventObject
  {
    return this._exceptionEventObject;
  }

  public function get unhandledExceptionEventObject():EventObject
  {
    return this._unhandledExceptionEventObject;
  }

  public function get outputEventObject():EventObject
  {
    return this._outputEventObject;
  }
}
}
